/***************************************************************************
 * arp.c 
 *
 *  Funciones para implementar ARP en las practicas de 
 *  Redes de Comunicaciones I de la EPS-UAM
 *
 *	Autores: Mario Alvarez y J. Angel Gonzalez
 * 
 *	Fecha: 23-11-2010
 *
 * ************************************************************************
 *  Diseño: Manuel Freire
 *
 *  (C) 2006-07 EPS-UAM 
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

#include "nivel1a.h"
#include "arp.h"
#include "rc_funcs.h"
#include "memAndSem.h"

// DEFINEs Y ENUMs INTERNOS

#define ARP_DATA_MIN 28										/*!< Longitud minima de trama ARP*/
#define ARP_HLEN 6											/*!< Longitud de la cabecera ARP*/
#define ARP_TLEN 2											/*!< Longitud del campo TYPE de ARP*/
#define ARP_REQ_TIMEOUT_US (1000*500)						/*!< Tiempo de espera a respuesta por ARP_REQUEST en microsegundos*/
#define ARP_REQ_TIMEOUT_NS ((ARP_REQ_TIMEOUT_US)*1000L)		/*!< Tiempo de espera a respuesta por ARP_REQUEST en nanosegundos*/
#define ARP_REQ_RETRIES 3									/*!< Numero de reintentos de ARP_REQUEST antes de desistir*/

/*Constantes de cabecera de ARP*/
enum {
    trama_arp_o_eth = 8,
    trama_arp_o_ip = 14,
    trama_arp_d_eth = 18,
    trama_arp_d_ip = 24
};

// VARIABLES GLOBALES INTERNAS

// dir ip local; exportada en 'ip.h', inicializada en arp_inicializa
BYTE dir_ip_local[IP_ALEN];

// dir ethernet local, inicializada en arp_inicializa
BYTE dir_eth_local[ETH_ALEN];

// dir ethernet para broadcast
BYTE dir_eth_any[ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// ethertype para tramas ARP
BYTE ethertype_arp[ETH_TLEN] = {0x08, 0x06};

// parte invariante de una trama ARP (IP sobre Ethernet)
BYTE cabecera_arp[ARP_HLEN] = {0, 1, 8, 0, 6, 4};

// tipo de mensaje ARP para solicitud (REQ)
BYTE arptype_req[ARP_TLEN] = {0x00, 0x01};

// tipo de mensaje ARP para respuesta (REP)
BYTE arptype_rep[ARP_TLEN] = {0x00, 0x02};

// si 1, ya se ha inicializado este nivel (y la dir. IP no estaba tomada)
int arp_inicializado = 0;
int ip_validada; // 1 = nadie ha respondido a un gratitious ARP

// entrada de cache ARP
typedef struct {
   time_t expiry;            // cuando llegue este momento, habra expirado
   BYTE dir_ip[IP_ALEN];        
   BYTE dir_eth[ETH_ALEN];   
} entrada_arp;

// cache ARP (global); todos los accesos deben estar protegidos mediante sem_tabla_arp
struct {
   entrada_arp t[ARP_CACHE_SIZE];
   int n;
} tabla_arp;
// ojo: no se puede incluir dentro de una estructura, porque es mas bien feo...
pthread_mutex_t sem_tabla_arp = PTHREAD_MUTEX_INITIALIZER;

// usada por arp_solicita_direccion_eth / arp_procesa_trama para peticiones en curso: 
// - el que solicita pone esperando_respuesta a 1 y guarda la direccion solicitada en dir_ip_solicitando
// - el que recibe lo lee, mete la respuesta en eth_pedida, deja esperando_respuesta a 0
// - el que pide encuentra que esperando_respuesta esta a 0, devuelve eth_pedida
int esperando_respuesta_arp; 				/*!< 1 = esperando una respuesta*/
BYTE dir_eth_respuesta_arp[ETH_ALEN];		/*!< Punto de retorno de la direccion ETHERNET solicitada*/
BYTE dir_ip_solicitando[IP_ALEN];			/*!< Direccion IP solicitante*/
short arp_sleeping=0;

pthread_mutex_t req_mutex = PTHREAD_MUTEX_INITIALIZER;						/*!< Semaforo de proteccion contra reentrada de la funcion de peticion traduccion ARP*/
pthread_mutex_t arp_sleeping_mutex = PTHREAD_MUTEX_INITIALIZER;

#define cpyEthDir(dst, src) (memcpy((dst), (src), ETH_ALEN))						/*!< Copia la direccion MAC src en la direccion MAC dst*/
#define cmpEthDir(dirA, dirB) (memcmp((dirA), (dirB), ETH_ALEN))					/*!< Compara direcciones MAC*/
#define isBroadcastEthDir(dir) (cmpEthDir((dir), dir_eth_any)==0 ? 1: 0)			/*!< Retorna 1 si la direccion MAC es de broadcast, 0 si no*/
#define cmpIpDir(dirA, dirB) (memcmp((dirA), (dirB), IP_ALEN))						/*!< Compara direcciones IP*/
#define cpyIpDir(dst, src) (memcpy((dst), (src), IP_ALEN))							/*!< Copia la direccion IP src en la direccion IP dst*/
#define isRequestType(type) (memcmp((type),arptype_req, ARP_TLEN)==0)				/*!< Retorna 1 si el tipo suministrado es de peticion, 0 si no*/

// PROTOTIPOS INTERNOS

// gestion de cache

void arp_limpia_cache();
int arp_busca_entrada_cache(BYTE *dir_ip, BYTE *dir_ether_buscada);
// NOTA: arp_actualiza_cache es publica: se llama desde IP...

// manejo de tramas ARP

int arp_lee_datos_trama(const BYTE *datos_trama, BYTE *tipo, 
    BYTE *dir_eth_origen, BYTE *dir_ip_origen, 
    BYTE *dir_eth_destino, BYTE *dir_ip_destino);

void arp_muestra_datos_trama(BYTE *datos_trama);

void arp_escribe_trama(BYTE *trama, BYTE *tipo, 
    BYTE *dir_eth_destino, BYTE *dir_ip_destino);

int arp_solicita_direccion_eth(BYTE *dir_ip, BYTE *dir_eth_pedida);

int arp_procesa_solicitud(BYTE *dir_eth_solicitante, BYTE *dir_ip_solicitante, BYTE *dir_ip_solicitud);
int arp_procesa_respuesta(BYTE *dir_eth_solicitud, BYTE *dir_ip_solicitud);
int arp_notify(const BYTE *dir_src, int size,const BYTE *msg, int type, struct timeval *timestamp);

// FUNCIONES DE GESTION DE CACHE

/****************************************************************************
 * Limpia correspondencias viejas
 * Basta con buscar la primera entrada no expirada (primeras = mas antiguas),
 * y eliminar todas las anteriores.
 *
 * entra/sale: nada
 ****************************************************************************/
void arp_limpia_cache() {
    int i;
    time_t t = time(NULL);
    
    pthread_mutex_lock(&sem_tabla_arp);    
    {    
        for (i=0; i<tabla_arp.n; i++) {
            if (t < tabla_arp.t[i].expiry) break;
        }
        
        if (i > 0) {
            memmove(tabla_arp.t, tabla_arp.t+i, 
                (tabla_arp.n - i) * sizeof(entrada_arp));
        }
        
        tabla_arp.n -= i;
    }    
    pthread_mutex_unlock(&sem_tabla_arp);    
}

/****************************************************************************
 * Actualiza la cache ARP con una nueva entrada. 
 * Si la entrada ya estaba presente, solo actualiza su 'tiempo de vida'
 * Si no tiene espacio para una nueva entrada, borra la entrada mas antigua.
 *
 * entra:
 *    dir_ip, dir_ether - direcciones a introducir en la nueva entrada
 * sale: nada
 ****************************************************************************/
void arp_actualiza_cache(BYTE *dir_ip, BYTE *dir_ether) {
    int i;
    
    arp_limpia_cache();
    
    pthread_mutex_lock(&sem_tabla_arp);    
    {
        // elimina entrada ya existente (si la hay)
        for (i=0; i<tabla_arp.n; i++) {
            if (memcmp(tabla_arp.t[i].dir_ip, dir_ip, IP_ALEN) == 0) {           
                memmove(tabla_arp.t+i, tabla_arp.t+i+1, 
                    (tabla_arp.n - i - 1) * sizeof(entrada_arp));       
                tabla_arp.n --;
                break;
            }
        }
        
        // necesaria entrada nueva; asegurar espacio
        if (tabla_arp.n == ARP_CACHE_SIZE) {
            memmove(tabla_arp.t, tabla_arp.t+1, 
                    (tabla_arp.n - 1) * sizeof(entrada_arp));       
            tabla_arp.n --;
        }
        
        memcpy(tabla_arp.t[tabla_arp.n].dir_ip, dir_ip, IP_ALEN);
        memcpy(tabla_arp.t[tabla_arp.n].dir_eth, dir_ether, ETH_ALEN);
        tabla_arp.t[tabla_arp.n].expiry = time(NULL) + ARP_CACHE_TTL;
        tabla_arp.n ++;
    }    
    pthread_mutex_unlock(&sem_tabla_arp);    
}

/****************************************************************************
 * Busca una entrada  la cache ARP con una nueva entrada. 
 *
 * entra:
 *    dir_ip - direccion IP cuya correspondiente dir. ethernet se busca
 *    dir_ether_buscada - direccion ethernet donde escribir el resultado
 * sale:
 *    0 si encontrada, -1 si no encontrada
 ****************************************************************************/
int arp_busca_entrada_cache(BYTE *dir_ip, BYTE *dir_ether_buscada) {
    int i, rc;
    
    arp_limpia_cache();
    
    pthread_mutex_lock(&sem_tabla_arp);    
    {
        rc = -1;
        for (i=0; i<tabla_arp.n; i++) {
            if (memcmp(tabla_arp.t[i].dir_ip, dir_ip, IP_ALEN) == 0) {
                memcpy(dir_ether_buscada, tabla_arp.t[i].dir_eth, ETH_ALEN);
                rc = 0;
                break;
            }
        }
    }
    pthread_mutex_unlock(&sem_tabla_arp);
    return rc;
}

/****************************************************************************
 * Muestra la cache ARP por stdout
 * entra / sale: nada
 ****************************************************************************/
void arp_muestra_cache() {
    int i;
    time_t t = time(NULL);
    
    pthread_mutex_lock(&sem_tabla_arp);
    {
        for (i=0; i<tabla_arp.n; i++) {
            // ignora las entradas que ya han expirado, pero no las limpia todavia
            if (t > tabla_arp.t[i].expiry) continue;
            
            printf("%3.3d.%3.3d.%3.3d.%3.3d    %2.2x.%2.2x.%2.2x.%2.2x.%2.2x.%2.2x    %d\n",
              tabla_arp.t[i].dir_ip[0], tabla_arp.t[i].dir_ip[1], 
              tabla_arp.t[i].dir_ip[2], tabla_arp.t[i].dir_ip[3], 
              tabla_arp.t[i].dir_eth[0], tabla_arp.t[i].dir_eth[1], tabla_arp.t[i].dir_eth[2],
              tabla_arp.t[i].dir_eth[3], tabla_arp.t[i].dir_eth[4], tabla_arp.t[i].dir_eth[5],
              (int)(tabla_arp.t[i].expiry - t));
        }
    }   
    pthread_mutex_unlock(&sem_tabla_arp);
}

// FUNCIONES DE GESTION DE TRAMAS ARP

/****************************************************************************
 * Muestra el contenido de una trama ARP por stdout
 * Si la cabecera ARP es correcta,
 *    Muestra las direcciones origen y destino, y el tipo de trama (REQ o REP)
 * Si la cabecera no es correcta,
 *    Muestra los bytes de la cabecera.
 *
 * entra:
 *    datos_trama - puntero al comienzo de los datos ARP de una trama Ethernet
 * sale:
 *    nada
 ****************************************************************************/
void arp_muestra_datos_trama(BYTE *datos_trama) {

    BYTE dir_eth_origen[ETH_ALEN], dir_ip_origen[IP_ALEN];
    BYTE dir_eth_destino[ETH_ALEN], dir_ip_destino[IP_ALEN];
    BYTE tipo[ARP_TLEN];
    BYTE *ip, *eth;
    int i;
    
    if (arp_lee_datos_trama(datos_trama, tipo, 
        dir_eth_origen, dir_ip_origen, dir_eth_destino, dir_ip_destino) != -1) {
        
        ip = dir_ip_origen;
        eth = dir_eth_origen;
        printf("%3.3d.%3.3d.%3.3d.%3.3d %2.2x.%2.2x.%2.2x.%2.2x.%2.2x.%2.2x -> ",
            ip[0], ip[1], ip[2], ip[3], 
            eth[0], eth[1], eth[2], eth[3], eth[4], eth[5]);
        ip = dir_ip_destino;
        eth = dir_eth_destino;
        printf("%3.3d.%3.3d.%3.3d.%3.3d %2.2x.%2.2x.%2.2x.%2.2x.%2.2x.%2.2x %s\n",
            ip[0], ip[1], ip[2], ip[3], 
            eth[0], eth[1], eth[2], eth[3], eth[4], eth[5], 
            (memcmp(tipo, arptype_req, ARP_TLEN)==0 ? "?" : "!"));
    }
    else {
        printf("Trama fea: ");
        for (i=0; i<ARP_HLEN; i++) {
            printf("%2.2x ", datos_trama[i]);
        }
        printf("\n");
    }                
}

/****************************************************************************
 * Lee los datos de una trama ARP
 *
 * entra:
 *    datos_trama - puntero al comienzo de los datos ARP de una trama Ethernet
 *    tipo, dir_eth_origen, dir_ip_origen, dir_eth_destino, dir_ip_destino - 
 *        punteros a los campos a rellenar con los datos correpondientes
 * sale:
 *    0 si todo bien, -1 si error (no son datos ARP validos)
 ****************************************************************************/
int arp_lee_datos_trama(const BYTE *datos_trama, BYTE *tipo, 
    BYTE *dir_eth_origen, BYTE *dir_ip_origen, 
    BYTE *dir_eth_destino, BYTE *dir_ip_destino)
{
    if(memcmp(datos_trama, cabecera_arp, ARP_HLEN)!=0){			/*Control de tramas para asegurar que la trama se cinne al protocolo ARP*/
    	return -1;
    }
    
    /*Extraccion de campos de la trama*/
	if(memcpy(dir_eth_origen, datos_trama+trama_arp_o_eth, ETH_ALEN)!=dir_eth_origen){
		return -1;
	}
	if(memcpy(dir_ip_origen, datos_trama+trama_arp_o_ip, IP_ALEN)!=dir_ip_origen){
		return -1;
	}
	if(memcpy(dir_eth_destino, datos_trama+trama_arp_d_eth, ETH_ALEN)!=dir_eth_destino){
		return -1;
	}
	if(memcpy(dir_ip_destino, datos_trama+trama_arp_d_ip, IP_ALEN)!=dir_ip_destino){
		return -1;
	}
	if(memcpy(tipo, datos_trama+ARP_HLEN, ARP_TLEN)!=tipo){
		return -1;
	}

    return 0;
}

/****************************************************************************
 * Prepara una trama ARP para ser enviada, con los datos que se soliciten
 *
 * entra:
 *    trama - trama Ethernet a preparar (la parte ARP empieza en trama+ETH_HLEN)
 *    tipo - tipo de peticion
 *    dir_eth_destino, dir_ip_destino - dir. a incluir en los campos de destino
 * sale:
 *    nada
 ****************************************************************************/
 
/**********************************************************************************************************************
    											 FORMATO DE TRAMA ARP
    											
                           _______________________________________________________________________________
                          |                   |                   |         |         |                   |    
          CABECERA ARP    |   TIPO HARDWARE   |  TIPO PROTOCOLO   | TAM. HW | TAM. PR |     OPERACION     |
       ___________________|___________________|___________________|_________|_________|___________________|
      |                                                           |                                       | 
      |                DIRECCION ETHERNET ORIGEN                  |         DIRECCION IP ORIGEN           | 
      |___________________________________________________________|_______________________________________| 
      |                                                           |                                       | 
      |                DIRECCION ETHERNET DESTINO                 |         DIRECCION IP DESTINO          | 
      |___________________________________________________________|_______________________________________| 

**********************************************************************************************************************/
void arp_escribe_trama(BYTE *trama, BYTE *tipo, 
    BYTE *dir_eth_destino, BYTE *dir_ip_destino) {

	/*Volcado de los campos a la trama*/
    memcpy(trama, cabecera_arp, ARP_HLEN);
	memcpy(trama+trama_arp_o_eth, dir_eth_local, ETH_ALEN);
	memcpy(trama+trama_arp_o_ip, dir_ip_local, IP_ALEN);
	memcpy(trama+trama_arp_d_eth, dir_eth_destino, ETH_ALEN);
	memcpy(trama+trama_arp_d_ip, dir_ip_destino, IP_ALEN);
	memcpy(trama+ARP_HLEN, tipo, ARP_TLEN);
	
	return;
}

// FUNCIONES PRINCIPALES DE ARP

/****************************************************************************
 * Procesa una trama Ethernet de tipo ARP
 * Si la direccion 'ip' del campo 'destino' es la propia,
 *    Si se trata de una consulta, envia una respuesta
 *    Si se trata de una respuesta, y el origen era la ip de una peticion 
 *       en curso, considera que ha satisfecho la peticion.
 * En cualquier caso, actualiza la cache con la ip/eth origen de la trama
 *
 * entra:
 *    tamano, trama - longitud y datos de la trama ethernet recibida
 *    dir_eth_nivel1 - direccion ethernet origen de la trama recibida
 * sale:
 *    0 si no hay fallos, -1 en caso contrario
 ****************************************************************************/
int arp_procesa_trama(int tamano, const BYTE *trama, const BYTE *dir_eth_nivel1)
{
	BYTE type[ARP_TLEN];
	BYTE dir_eth_src[ETH_ALEN];
	BYTE dir_ip_src[IP_ALEN];
	BYTE dir_eth_dst[ETH_ALEN];
	BYTE dir_ip_dst[IP_ALEN];

	/*******************************TERMINADO****************************************/
	
    /* PRACTICA: implementa la parte correspondiente de ARP, teniendo en cuenta      */
    /* ... notificar error si la dir_eth_nivel1 no corresponde a la dir. eth. origen */
    /* ... ignorar peticiones cuya dir. eth. origen somos nosotros (ARP gratuito)    */
    /* ... usar arp_escribe_trama para responder a peticiones dirigidas a esta IP    */
    /* (elimina tambien el siguiente "printf")                                       */
    
	if(trama==NULL || dir_eth_nivel1==NULL || tamano<ARP_DATA_MIN){
		return -1;
	}
	
	if(arp_lee_datos_trama(trama, type, dir_eth_src, dir_ip_src, dir_eth_dst, dir_ip_dst)!=0){
		return -1;
	}
	
	if(cmpEthDir(dir_eth_nivel1, dir_eth_src)!=0){				/*No coincide la direccion origen con el campo ARP origen*/
		return -1;
	}
	
	if(cmpEthDir(dir_eth_nivel1, dir_eth_local)==0){			/*ARP gratuito*/
		return 0;
	}
	
	if(isRequestType(type)){
		return arp_procesa_solicitud(dir_eth_src, dir_ip_src, dir_ip_dst);								/*ARP_REQUEST*/
	}
	else if(cmpEthDir(dir_eth_local, dir_eth_dst)==0 && cmpIpDir(dir_ip_local, dir_ip_dst)==0){
		return arp_procesa_respuesta(dir_eth_src, dir_ip_src);											/*ARP_REPLY*/
	} else{
		return -1;																						/*ARP_REPLY con direccionamiento incorrecto*/
	}
}

/****************************************************************************
 * Procesa una trama Ethernet de tipo ARP_REQUEST
 *
 * Comprueba la correspondencia entre la IP local y la IP solicitada y, si
 * coinciden, envia un ARP_REPLY. Actualiza la cache ARP con la direccion
 * solicitante.
 *
 * entra:
 *    dir_eth_solicitante - Direccion ETHERNET de la estacion solicitante
 *    dir_ip_solicitante - Direccion IP de la estacion solicitante
 *	  dir_ip_solicitud - Direccion IP solicitada
 *
 * sale:
 *    0 si no hay fallos, -1 en caso contrario
 ****************************************************************************/
int arp_procesa_solicitud(BYTE *dir_eth_solicitante, BYTE *dir_ip_solicitante, BYTE *dir_ip_solicitud)
{
	BYTE arp_reply_frame[ARP_DATA_MIN];

	/*No se actualiza la cache con los ARP gratuitos (Aunque sean externos), para evitar el envenenamiento de cache*/
	if(cmpIpDir(dir_ip_solicitante, dir_ip_solicitud)!=0){
		arp_actualiza_cache(dir_ip_solicitante, dir_eth_solicitante);
	}

	if(cmpIpDir(dir_ip_solicitud, dir_ip_local)!=0){		/*Finaliza el procesado si la trama no solicita la IP local*/
		return 0;
	}

	/*Responde con un ARP_REPLY a la solicitud de traduccion*/
	arp_escribe_trama(arp_reply_frame, arptype_rep, dir_eth_solicitante, dir_ip_solicitante);
	if(EnviarDatagramaNivel1a(dir_eth_solicitante, ARP_DATA_MIN, arp_reply_frame, ARP_N1a_TYPE)!=ETH_OK){
		return -1;
	}
	
	return 0;
}

/****************************************************************************
 * Procesa una trama Ethernet de tipo ARP_REPLY
 *
 * Recibe un ARP_REQUEST respondiendo a una peticion ARP previa. Despierta
 * al hilo bloqueado a la espera de respuesta a ARP_REQUEST.
 *
 * entra:
 *	  dir_eth_solicitud - Direccion MAC solicitada
 *	  dir_ip_solicitud - Direccion IP solicitada
 *
 * sale:
 *    0 si no hay fallos, -1 en caso contrario
 ****************************************************************************/
int arp_procesa_respuesta(BYTE *dir_eth_solicitud, BYTE *dir_ip_solicitud)
{
	if(!esperando_respuesta_arp || cmpIpDir(dir_ip_solicitud, dir_ip_solicitando)!=0 || isBroadcastEthDir(dir_eth_solicitud)){
		return -1;
	}
	
	/*Copia la respuesta a la variable de intercambio, actualiza la cache y marca la peticion como servida*/
	cpyEthDir(dir_eth_respuesta_arp, dir_eth_solicitud);
	arp_actualiza_cache(dir_ip_solicitud, dir_eth_solicitud);
	esperando_respuesta_arp=0;

    pthread_mutex_lock(&arp_sleeping_mutex);																						
    {
    	if(arp_sleeping){
			/*Despierta al hilo bloqueado a la espera de respuesta a ARP_REQUEST*/	
			if(wakeup(getpid())!=MEM_AND_SEM_OK){
				pthread_mutex_unlock(&arp_sleeping_mutex);																													
				return -1;
			}
		}
	}
    pthread_mutex_unlock(&arp_sleeping_mutex);																													

	return 0;
}

/****************************************************************************
 * Solicita la direccion Ethernet que corresponde a una direccion IP.
 * Si la direccion pedida ya esta en la cache, la devuelve sin mas.
 * En caso contrario, la solicita enviando una trama REQ, y reintentando
 * varias veces antes de desistir.
 *
 * entra:
 *    dir_ip - direccion IP
 *    dir_ether_pedida - direccion Ethernet a rellenar
 * sale:
 *    0 y dir_ether_pedida a su valor correspondiente, o -1 si error
 ****************************************************************************/
int arp_solicita_direccion(BYTE *dir_ip, BYTE *dir_eth_pedida) {
    
    // comprueba si estan pidiendo la dir. propia
    if (memcmp(dir_ip, dir_ip_local, IP_ALEN) == 0) {
        memcpy(dir_eth_pedida, dir_eth_local, ETH_ALEN);
        return 0;
    }
    
    // busca en la cache
    if (arp_busca_entrada_cache(dir_ip, dir_eth_pedida) == 0) {
        // encontrada en cache
        return 0;
    }
    
    // busca en la red mediante ARP
    return arp_solicita_direccion_eth(dir_ip, dir_eth_pedida);
}
    
/****************************************************************************
 * Solicita una direccion ARP directamente al exterior; no usa cache. Esto
 * es util para implementar ARP gratuito, por ejemplo.
 *
 * NOTA: Esta funcion ha sido protegida contra reentrada. Solo se permite
 * el acceso concurrente de un hilo a la zona critica.
 *
 * entra:
 *    dir_ip - direccion IP
 *    dir_ether_pedida - direccion Ethernet a rellenar
 * sale:
 *    0 y dir_ether_pedida a su valor correspondiente, o -1 si error 
 ****************************************************************************/
int arp_solicita_direccion_eth(BYTE *dir_ip, BYTE *dir_eth_pedida)
{    
	int trials=0;
	BYTE arp_req_frame[ARP_DATA_MIN];
	int status;
    /* PRACTICA: implementa esta funcion, con ARP_REQ_RETRIES cada ARP_REQ_TIMEOUT_US */
    /*   (usa 'usleep' en lugar de 'sleep') antes de devolver error.                  */
    /* (y elimina tambien el siguiente "printf")                                      */
    
    if(dir_ip==NULL || dir_eth_pedida==NULL){
    	return -1;
    }
    
    arp_escribe_trama(arp_req_frame, arptype_req, dir_eth_any, dir_ip);			/*Creacion de ARP_REQUEST*/
    
    pthread_mutex_lock(&req_mutex);																						
    {
		/*Creacion de una solicitud ARP*/
        memcpy(dir_ip_solicitando, dir_ip, IP_ALEN);
 	    esperando_respuesta_arp=1;
    	
    	do{
   			status=EnviarDatagramaNivel1a(dir_eth_any, ARP_DATA_MIN, arp_req_frame, ARP_N1a_TYPE);			/*Envio de ARP_REQUEST*/
   			if(status!=ETH_OK){
   				continue;
   			}

   			pthread_mutex_lock(&arp_sleeping_mutex);																						
    		{
    			arp_sleeping=1;
    		}
    		pthread_mutex_unlock(&arp_sleeping_mutex);
    		
			status=sleepUntilWakeup(ARP_REQ_TIMEOUT_NS);   				/*Bloqueo del proceso a la espera de ARP_REPLY*/
			
			pthread_mutex_lock(&arp_sleeping_mutex);																						
    		{
    			arp_sleeping=0;
    		}
    		pthread_mutex_unlock(&arp_sleeping_mutex);
			
   			if(esperando_respuesta_arp==1){
   				status=-1;
   			}
			trials++;
		}while(trials<ARP_REQ_RETRIES && status==-1);
 	
 		cpyEthDir(dir_eth_pedida, dir_eth_respuesta_arp);				/*Rescate de la direccion de respuesta*/
   	
   	}
    pthread_mutex_unlock(&req_mutex);	
   	
   	if(trials>=ARP_REQ_RETRIES && status==-1){
   		return -1;
   	}
   	
    return 0;
}

/****************************************************************************
 * Inicializa ARP; debe llamarse antes de usar otras funciones de 'arp.h'
 * Se puede llamar repetidas veces sin efectos adversos
 *
 * Lee la variable de entorno 'IPLOCAL' para determinar la IP local.
 *
 * entra: nada
 * sale:
 *   0 si todo bien, -1 en caso de error
 ****************************************************************************/
int arp_inicializa()
{
	BYTE dir_eth_arp_gratuito[ETH_ALEN];
	
	if (arp_inicializado) {
		return 0;
  	}
  
  	/*Inicializacion del Nivel1a*/
    if(InicializarNivel1a(0, 1, arp_notify, 0)!=ETH_OK){
    	return -1;
    }	

    // obtiene dir. ethernet local
    if(ObtenerDirMAC(dir_eth_local) != ETH_OK) {
        fprintf(stderr, "Error obteniendo dir MAC local\n");
	FinalizarNivel1a();
        return -1;
    }    

    // obtiene ip local
    if(lee_cadena_ip(getenv("IPLOCAL"), dir_ip_local) != 0) {
        fprintf(stderr, "Error obteniendo dir IP local\n");
	FinalizarNivel1a();
        return -1;
    }
    
    
    arp_inicializado = 1;
    
    /*Lanzamiento de ARP gratuito*/
    if(arp_solicita_direccion_eth(dir_ip_local, dir_eth_arp_gratuito)==0){
    	arp_inicializado=0;
    	fprintf(stderr, "Error al lanzar IP gratuito: La direccion IP local ya existe en el enlace\n");
    }
    
    fprintf(stderr, "Inicializado: %3.3d.%3.3d.%3.3d.%3.3d / %2.2x.%2.2x.%2.2x.%2.2x.%2.2x.%2.2x\n",
        dir_ip_local[0], dir_ip_local[1], dir_ip_local[2], dir_ip_local[3], 
        dir_eth_local[0], dir_eth_local[1], dir_eth_local[2], 
        dir_eth_local[3], dir_eth_local[4], dir_eth_local[5]);        
    
  	return 0;
}

/****************************************************************************
 * Finaliza ARP; debe para liberar la EdD asociadas a la biblioteca.
 * Se puede llamar repetidas veces sin efectos adversos.
 *
 *
 * entra: nada
 * 
 * sale:
 *		0 si todo bien, -1 en caso de error
 ****************************************************************************/
int arp_finaliza()
{
	if(!arp_inicializado){
		return 0;
	}

	arp_inicializado=0;
	
	if(FinalizarNivel1a()!=ETH_OK){
		return -1;
	}	
	
	return 0;
}

/****************************************************************************
 * Funcion de notificacion para ARP desde el Nivel 1a. Es activada cuando se
 * recibe una trama ARP. Redirecciona el flujo hacia la funcion de procesado
 * de trama.
 *
 *
 * entra:
 *		dir_src - Direccion MAC de la estacion origen de la trama
 *		size - Tamanno de la trama
 *		msg - Mensaje de la trama
 *		type - Tipo ETHERNET de la trama
 *		timestamp - Fecha de recepcion de la trama
 *
 * sale:
 *		ETH_OK si se debe aceptar la trama, ETH_ERROR si se debe rechazar la
 * trama.
 ****************************************************************************/
int arp_notify(const BYTE *dir_src, int size,const BYTE *msg, int type, struct timeval *timestamp)
{
	if(dir_src==NULL || msg==NULL || size<0 || type!=ARP_N1a_TYPE){
		return ETH_ERROR;
	}
	
	arp_procesa_trama(size, msg, dir_src);
	
	return ETH_ERROR;
}
